﻿using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MugenInjection;
using MugenInjection.Attributes;
using MugenInjection.Bindings;
using MugenInjection.Exceptions;
using MugenInjection.Interface;
using MugenInjection.Parameters;
using MugenInjection.Scope;
using TestModels.Class;
using Should;

namespace MugenInjectionTest.Core
{
    public partial class InjectorResolverTest
    {
        [TestMethod]
        public void MultiBindingTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<IInt1, IInt2, IInt3>().To<MultiInterface>();
                injector.Get<IInt1>().ShouldBeType<MultiInterface>();
                injector.Get<IInt2>().ShouldBeType<MultiInterface>();
                injector.Get<IInt3>().ShouldBeType<MultiInterface>();
            }
        }

        [TestMethod]
        public void ParametersTest()
        {
            const string value = "Test";
            const string propValueFromBinding = "propValueFromBinding";

            var empty = new Empty();
            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<IEmptyInterface>().ToConstant(empty);
                injector.Bind<string>().ToConstant(value);

#if NOEXPRESSION
                injector.Bind<TestParameter>().ToSelf().WithPropertyValue("Property", propValueFromBinding);
#else
                injector.Bind<TestParameter>().ToSelf().WithPropertyValue(parameter => parameter.Property,
                                                                                "propValueFromBinding");
#endif


                var test = injector.Get<TestParameter>();
                test.Property.ShouldEqual(propValueFromBinding);
                test.MethodParameter.ShouldEqual(value);

                var newEmpty = new Empty();
                const string newValue = "newValue";
                test = injector.Get<TestParameter>(new MethodParameter("Method", "parameter", newValue),
                                                   new PropertyParameter("Property", newValue));
                test.Property.ShouldEqual(newValue);
                test.MethodParameter.ShouldEqual(newValue);
            }
        }

        [TestMethod]
        public void NotBindableTypesTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.CanResolve(typeof(string)).ShouldBeFalse();
                injector.CanResolve(typeof(StaticClass)).ShouldBeFalse();
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get(typeof(string)));
                TestUtils.Catch<BindingNotFoundException>(() => injector.Get(typeof(StaticClass)));
            }
        }

        [TestMethod]
        public void GenericInterfaceTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.Bind(typeof(ISimpleGeneric<>)).To(typeof(SimpleGeneric<>));
                injector.CanResolve(typeof(ISimpleGeneric<string>)).ShouldBeTrue();
                var simpleGeneric = injector.Get<ISimpleGeneric<string>>();
                simpleGeneric.ShouldNotBeNull();

                injector.Bind(typeof(ITwoGenericArg<,>)).To(typeof(TwoGenericArg<,>));
                injector.CanResolve(typeof(ITwoGenericArg<object, string>)).ShouldBeTrue();
                var twoGenericArg = injector.Get<ITwoGenericArg<object, string>>();
                twoGenericArg.ShouldNotBeNull();
                injector.Unbind(typeof(ITwoGenericArg<,>));

                injector.Bind<ITwoGenericArg<string, object>>().To<TwoGenericArg<string, object>>();
                injector.CanResolve(typeof(ITwoGenericArg<object, string>)).ShouldBeFalse();
                injector.CanResolve(typeof(ITwoGenericArg<string, object>)).ShouldBeTrue();
                var result = injector.Get<ITwoGenericArg<string, object>>();
                result.ShouldNotBeNull();

                //Self bindable
                var generic = injector.Get<SimpleGeneric<string>>();
                generic.ShouldNotBeNull();
                injector.Get<SimpleGeneric<SimpleGeneric<string>>>().ShouldNotBeNull();

                //Error
                TestUtils.Catch<Exception>(() => injector.Get(typeof(ITwoGenericArg<,>)));
            }
        }

        [TestMethod]
        public void InjectTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                const string constructorParam = "constructorParam";
                const string propertyParam = "PropertyTest";
                const string methodParam = "methodParam";


                var parameters = new List<IInjectionParameter>
                                     {
                                         new ConstructorParameter("constructorTest", constructorParam),
                                         new PropertyParameter("PropertyTest", propertyParam),
                                         new MethodParameter("MethodTest", "test", methodParam)
                                     };
                var activate = new ActivatorTestModel();
                injector.Inject(ref activate, parameters.ToArray());
                activate.Property.ShouldEqual(propertyParam);
                activate.Method.ShouldEqual(methodParam);
                
                const string injectParameter = "Test";
                injector.Bind<string>().ToConstant(injectParameter);

                //Clear parameter
                parameters.Clear();

                activate = new ActivatorTestModel();
                injector.Inject(ref activate);
                activate.Property.ShouldEqual(injectParameter);
                activate.Method.ShouldEqual(injectParameter);                
            }
        }

        [TestMethod]
        public void PriorityTest()
        {
            var high = new Empty();
            var low = new Empty();

            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<Empty>().ToConstant(high).SetHighPriority();
                injector.Bind<Empty>().ToConstant(low).SetLowPriority();

                var resolve = injector.Get<Empty>();
                resolve.ShouldEqual(high);
            }
        }

        [TestMethod]
        public void TestOptionalParameters()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.Bind<OptionalParametersClass>().ToSelf();

                var parametersClass = injector.Get<OptionalParametersClass>();
                parametersClass.Test.ShouldEqual("test");
                parametersClass.MethodP.ShouldEqual("test");

                injector.Bind<string>().ToConstant("1");
                parametersClass = injector.Get<OptionalParametersClass>();
                parametersClass.Test.ShouldEqual("1");
                parametersClass.MethodP.ShouldEqual("1");
            }
        }

        #region Nested type: Empty

        public class Empty : IEmptyInterface
        {
        }

        #endregion

        #region Nested type: IEmptyInterface

        public interface IEmptyInterface
        {
        }

        #endregion

        #region Nested type: IInt1

        public interface IInt1
        {
        }

        #endregion

        #region Nested type: IInt2

        public interface IInt2
        {
        }

        #endregion

        #region Nested type: IInt3

        public interface IInt3
        {
        }

        #endregion

        #region Nested type: ISimpleGeneric

        public interface ISimpleGeneric<T>
        {
        }

        #endregion

        #region Nested type: ITwoGenericArg

        public interface ITwoGenericArg<T, T1>
        {
        }

        #endregion

        #region Nested type: MultiInterface

        public class MultiInterface : IInt1, IInt2, IInt3
        {
        }

        #endregion

        #region Nested type: OptionalParametersClass

        public class OptionalParametersClass
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="T:System.Object"/> class.
            /// </summary>
            public OptionalParametersClass(string test = "test")
            {
                Test = test;
            }

            public string Test { get; set; }
            public string MethodP { get; set; }

            [Inject]
            public void Method(string method = "test")
            {
                MethodP = method;
            }
        }

        #endregion

        #region Nested type: SimpleGeneric

        public class SimpleGeneric<T> : ISimpleGeneric<T>
        {
        }

        #endregion

        #region Nested type: StaticClass

        public static class StaticClass
        {
        }

        #endregion

        #region Nested type: TestParameter

        public class TestParameter
        {
            public string MethodParameter;

            [Inject]
            public string Property { get; set; }

            [Inject]
            public void Method(string parameter)
            {
                MethodParameter = parameter;
            }
        }

        #endregion

        #region Nested type: TwoGenericArg

        public class TwoGenericArg<T, T1> : ITwoGenericArg<T, T1>
        {
        }

        #endregion
    }
}